/*
Simple Forum Auto Poster.
Copyright (C) 2011 Gianni Van Hoecke.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package domain.forumdata.forums;

import domain.DomainController;
import domain.enums.ForumType;
import domain.forumdata.Category;
import domain.forumdata.Forum;
import domain.forumdata.ForumCategory;
import domain.forumdata.ForumUtils;
import domain.forumdata.SupportedForums;
import domain.forumdata.tag.Tag;
import domain.net.CaptchaUtils;
import domain.net.HTMLParser;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import persistence.net.Client;

/**
 *
 * @author Gianni Van Hoecke <gianni.vh@gmail.com>
 *
 * Represents <i>any</i> forum.
 * 
 * This class will allow you to (or attempt to) post to <i>any</i> forum.
 * It will try to search for the login form and post form.
 * It is not guaranteed that this will work for all forums, as the class is just guessing.
 *
 * When a CAPTCHA code is found, it will ask the user for input.
 *
 * Tested forums and confirmed working* are:
 *  - phpBB 2 (Free forum software)
 *  - phpBB 3 (Free forum software)
 *  - MyBB (Free forum software)
 *  - SMF (Simple Machines Forum) (Free forum software)
 *  - vBulletin (Commercial forum software)
 *  - IPB3 (Invision Power Boards 3) (Commercial forum software)
 *  - XenForo (Commercial forum software)
 *  - Woltlab Burning Board (Commercial forum software)
 * 
 * Still have to test:
 *  - ...
 *
 *  <i>* = confirmed working: tested on new installations.</i>
 */
public class Generic extends Forum
{
    transient private Client client; //Never ever save this to the disk.

    /**
     * Default constructor. Initializes a new forum of <i>any</i> type.
     * @param forumName The name for the forum.
     * @param loginUrl The URL to the login page.
     * @param username The username.
     * @param password The password.
     * @param categories A list of assigned categories.
     */
    public Generic(String forumName, String loginUrl, String username, char[] password, ArrayList<ForumCategory> categories)
    {
        super(SupportedForums.SUPPORTED_FORUMS[0], forumName, loginUrl, username, password, categories);
    }

    /**
     * Starts posting to the forum.
     * @param cat The category to post to.
     * @param topicTitle The topic title of the new post.
     * @param topicSubtitle The topic subtitle or small description of the new post.
     * @param tag The tag to use.
     * @param post The post body of the new post.
     * @return True if the message has been posted, false otherwise.
     */
    @Override
    public boolean doPost(Category cat, String topicTitle, String topicSubtitle, Tag tag, String post)
    {
        //Create new client
        this.client = new Client();

        //Login
        DomainController.getInstance().getLogger().log(Level.INFO, "Logging in at {0}...", this.getForumName());

        if(this.login())
        {
            //Get the correct new topic link
            String newTopicURL = "";
            for(ForumCategory fc : this.getCategories())
            {
                if(fc.getCategory().getName().equals(cat.getName()))
                {
                    newTopicURL = fc.getUrl();
                    break;
                }
            }

            //Post!
            DomainController.getInstance().getLogger().log(Level.INFO, "Creating new topic at {0}...", this.getForumName());
            return this.post(newTopicURL, topicTitle, topicSubtitle, tag, post);
        }
        else
        {
            DomainController.getInstance().getLogger().log(Level.WARNING, "Login failed at {0}!", this.getForumName());
            DomainController.getInstance().getLogger().log(Level.INFO, "Got response: {0}", this.client.getResponseContent());
            DomainController.getInstance().getErrorCollection().addError(this, false, false, this.client.getResponseContent());
        }
        return false;
    }

    /**
     * Tries to login at this forum.
     * @return True if the login was successful, false oterhwise.
     */
    private boolean login()
    {
        try
        {
            List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();

            //Download the login page.
            this.client.doGet(this.getLoginURL());
            String content = this.client.getResponseContent();

            //Extract all form input tags.
            List<NameValuePair> extractedNameValuePairs = HTMLParser.getFormInputs(new StringReader(content));

            //Analyze the tags.
            for(NameValuePair nvp : extractedNameValuePairs)
            {
                if(ForumUtils.isUsernameField(nvp.getName()))
                {
                    nameValuePairs.add(new BasicNameValuePair(nvp.getName(), this.getUsername()));
                }
                else if(ForumUtils.isPasswordField(nvp.getName()))
                {
                    nameValuePairs.add(new BasicNameValuePair(nvp.getName(), new String(this.getPassword())));
                }
                else if(nvp.getValue() == null || nvp.getValue().trim().equals("0"))
                {
                    //Ignore this.
                }
                else if(nvp.getName().equals("register"))
                {
                    nameValuePairs.add(new BasicNameValuePair(nvp.getName(), "0"));
                }
                else if((nvp.getName().equals("action") && nvp.getValue().equals("login")) ||
                        (nvp.getName().equals("do") && nvp.getValue().equals("process")) ||
                        (nvp.getName().equals("do") && nvp.getValue().equals("dismissnotice")) ||
                        (nvp.getName().equals("q") && nvp.getValue().equals("Enter search word")) ||
                        (nvp.getName().equals("types[]") && nvp.getValue().equals("post")) ||
                        (nvp.getName().equals("page") && nvp.getValue().equals("Index")))
                {
                    //Ignore this.
                }
                else if(ForumUtils.isSearchField(nvp.getValue()) || 
                        ForumUtils.isSearchField(nvp.getName()) ||
                        ForumUtils.isResetField(nvp.getName()))
                {
                    //Ignore this.
                }
                else
                {
                    //nameValuePairs.add(new BasicNameValuePair(nvp.getName(), nvp.getValue().replaceAll(" ", "+")));
                    nameValuePairs.add(nvp);
                }
            }

            /*
            System.out.println();
            for(NameValuePair nvp : nameValuePairs)
                System.out.print(nvp.getName() + "=" + nvp.getValue() + "&");
            System.out.println();
            */
            
            //Post
            Thread.sleep(2500); //Sometimes needed
            //Check if we're dealing with XenForo.
            if(this.getLoginURL().endsWith("/index.php?login/"))
                this.client.doPost(this.getLoginURL() + "login", nameValuePairs);
            else
                this.client.doPost(this.getLoginURL(), nameValuePairs);

            //Check if the login has succeeded.
            //System.out.println(this.client.getResponseContent());
            return ForumUtils.isLoginSuccessful(this.client.getResponseContent());
        }
        catch(Exception e)
        {
            DomainController.getInstance().getLogger().log(Level.SEVERE, "Login failed at {0}! ({1})", new String[]{this.getForumName(), e.getMessage()});
            return false;
        }
    }

    /**
     * Tries to post a new topic at this forum.
     * When successful, the method tries to find the link of the new topic and stores it.
     * @param url The url of the "new topic".
     * @param topicTitle The title for the new topic.
     * @param topicSubtitle The subtitle or small description for the new topic.
     * @param tag The tag to use.
     * @param post The message for the new topic.
     * @return True if the message has been posted, false otherwise.
     */
    private boolean post(String url, String topicTitle, String topicSubtitle, Tag tag, String post)
    {
        try
        {
            List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
            
            //Download new topic page.
            this.client.doGet(url);
            String content = this.client.getResponseContent();

            //Extract form input tags.
            List<NameValuePair> extractedNameValuePairs = HTMLParser.getFormInputs(new StringReader(content));

            //Analyze the tags.
            boolean iconAdded = false;
            boolean topicTypeAdded = false;
            for(NameValuePair nvp : extractedNameValuePairs)
            {
                if(ForumUtils.isSubjectField(nvp.getName()))
                {
                    nameValuePairs.add(new BasicNameValuePair(nvp.getName(), tag.getSynonyms()[0] + " " + topicTitle));
                }
                else if(ForumUtils.isDescriptionField(nvp.getName()))
                {
                    nameValuePairs.add(new BasicNameValuePair(nvp.getName(), topicSubtitle));
                }
                else if(ForumUtils.isMessageField(nvp.getName()))
                {
                    nameValuePairs.add(new BasicNameValuePair(nvp.getName(), post));
                }
                else if(ForumUtils.isTopicTypeField(nvp.getName()))
                {
                    if(!topicTypeAdded)
                    {
                        nameValuePairs.add(new BasicNameValuePair(nvp.getName(), "0"));
                        topicTypeAdded = true;
                    }
                }
                else if(ForumUtils.isIconField(nvp.getName()))
                {
                    if(!iconAdded)
                    {
                        nameValuePairs.add(new BasicNameValuePair(nvp.getName(), "0"));
                        iconAdded = true;
                    }
                }
                else if(nvp.getValue() == null || nvp.getValue().trim().equals("0"))
                {
                    //Ignore this.
                }
                else if((nvp.getName().equals("do") && nvp.getValue().equals("process")) ||
                        (nvp.getName().equals("do") && nvp.getValue().equals("dst")) ||
                        (nvp.getName().equals("q") && nvp.getValue().equals("Enter search word")) ||
                        (nvp.getName().equals("types[]") && nvp.getValue().equals("post")) ||
                        (nvp.getName().equals("page") && nvp.getValue().equals("Index")))
                {
                    //Ignore this.
                }
                //IGNORE PREVIEW AND SAVE DRAFT BUTTON!
                else if(ForumUtils.isPreviewField(nvp.getName()) || 
                        ForumUtils.isSaveDraftField(nvp.getName()) ||
                        ForumUtils.isPostPollField(nvp.getName()) ||
                        ForumUtils.isLockPostField(nvp.getName()) ||
                        ForumUtils.isSearchField(nvp.getValue()) ||
                        ForumUtils.isSearchField(nvp.getName()) ||
                        ForumUtils.isResetField(nvp.getName()))
                {
                    //ignore this entry!
                }
                else
                {
                    nameValuePairs.add(nvp);
                }
            }

            /*
            System.out.println();
            for(NameValuePair nvp : nameValuePairs)
                System.out.print(nvp.getName() + "=" + nvp.getValue() + "&");
            System.out.println();
            */
            
            /*
             * Sometimes we need to enter a captcha code
             * recaptcha_response_field
             */
            if(content.contains("recaptcha_response_field"))
            {
                try
                {
                    DomainController.getInstance().getLogger().log(Level.INFO, "Found CAPTCHA for {0}", this.getForumName());
                    nameValuePairs.addAll(CaptchaUtils.evaluateRecaptcha(client, content));
                }
                catch(Exception e)
                {
                    e.printStackTrace();
                    DomainController.getInstance().getLogger().log(Level.WARNING, "Captcha error at {0}. {1}", new String[]{this.getForumName(), e.getMessage()});
                }
            }

            //Post
            Thread.sleep(2500); //Needed for some unknown reason. (time between GET -> POST too short?)
            //Check if we're dealing with XenForo.
            if(url.contains("/index.php?forums/") && url.endsWith("/create-thread"))
                this.client.doPost(url.replace("/create-thread", "/add-thread"), nameValuePairs);
            //Check if we're dealing with SMF
            else if(url.contains("/index.php?action=post;board="))
                this.client.doPost(url.replace("action=post", "action=post2"), nameValuePairs);
            else
                this.client.doPost(url, nameValuePairs);
            //System.out.println(this.client.getResponseContent());

            //Check for success.
            if(ForumUtils.isPostSuccessful(this.client.getResponseContent()))
            {
                //Get the topic url.
                List<String> hrefs = HTMLParser.getHREF(new StringReader(this.client.getResponseContent()));

                //int endIndex = (this.getLoginURL().startsWith("http://") ? this.getLoginURL().substring(8).indexOf("/") : this.getLoginURL().indexOf("/"));
                int endIndex = this.getLoginURL().lastIndexOf("/") + 1;

                String link = this.getLoginURL().substring(0, endIndex);
                for(String s : hrefs)
                {
                    if(s != null && ForumUtils.isViewTopicIndocator(s))
                    {
                        //Check href.
                        if(s.startsWith("http://"))
                            link = s;
                        //Remove the './', if any.
                        else if(s.startsWith("./"))
                            link += s.substring(2);
                        else
                            link += s;
                        
                        break;
                    }
                }
                DomainController.getInstance().getLastPostTopicURLResults().add(link);
                DomainController.getInstance().getLogger().log(Level.INFO, "SUCCESS! Got link: {0}", link);
                return true;
            }
            //If we're dealing with SMF or vBulletin...
            //We'll get something like "[...]/index.php?topic=8.new#new" as URL. (on success) [SMF]
            //We'll get something like "[...]/showthread.php?4-mijn-test&p=4#post4" (on success) [vBulletin]
            //We'll get something like "[...]/index.php?/topic/3-titlehere/" (on success) [IPB3]
            //We'll get something like "[...]/index.php?threads/test.2/" (on succes) [XenForo]
            //We'll get something like "[...]/index.php?page=Thread&threadID=2" (on succes) [Woltlab Burning Board]
            else if(ForumUtils.isPostSuccessful(this.client.getResponseURL(), ForumType.SMF) ||
                    ForumUtils.isPostSuccessful(this.client.getResponseURL(), ForumType.VBULLETIN) ||
                    ForumUtils.isPostSuccessful(this.client.getResponseURL(), ForumType.IPB3) ||
                    ForumUtils.isPostSuccessful(this.client.getResponseURL(), ForumType.XENFORO) ||
                    ForumUtils.isPostSuccessful(this.client.getResponseURL(), ForumType.WOLTLAB_BURNING_BOARD) ||
                    ForumUtils.isPostSuccessful(this.client.getResponseContent(), ForumType.PHPBB3))
            {
                DomainController.getInstance().getLastPostTopicURLResults().add(this.client.getResponseURL());
                DomainController.getInstance().getLogger().log(Level.INFO, "SUCCESS! Got link: {0}", this.client.getResponseURL());
                return true;
            }
            else
            {
                DomainController.getInstance().getLogger().log(Level.WARNING, "Posting failed at {0}!", this.getForumName());
                DomainController.getInstance().getLogger().log(Level.INFO, "Got response: {0}", this.client.getResponseContent());
                DomainController.getInstance().getErrorCollection().addError(this, true, false, this.client.getResponseContent());
            }
        }
        catch(Exception e)
        {
            e.printStackTrace();
            DomainController.getInstance().getLogger().log(Level.SEVERE, "Posting failed at {0}! ({1})", new String[]{this.getForumName(), e.getMessage()});
            DomainController.getInstance().getErrorCollection().addError(this, false, false, this.client.getResponseContent());
        }
        return false;
    }

    @Override
    public boolean canLogin()
    {
        this.client = new Client();
        return this.login();
    }
}